/**
 ******************************************************************************
 * @file	ad5686-chip.c
 * @brief	ad5686 DAC功能测试
 *
 * ad5686.elf -D /dev/spidev4.0 -C 0 -c 5 -v 60000
 *
 * @section Depend
 *	-# spidev.ko
 *	-# libad5686.so
 *
 * 2023-01-17,MenglongWu,MenglongWoo@aliyun.com
 */


#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#include "ad5686.h"
#include "proginfo.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))


int          format_chip(struct ad5686_dev *dev);
unsigned int write_chip(struct ad5686_dev *dev, uint32_t addr, unsigned char *pdata, unsigned int len);
unsigned int read_chip(struct ad5686_dev *dev, uint32_t addr, unsigned char *pdata, unsigned int len);
unsigned int ap_hash(unsigned char *data, unsigned int len);

#define DEF_SPI "/dev/spidev1.1"
static char        *device  = DEF_SPI;
static uint8_t      mode    = 's';
static unsigned int onebyte = 0xf0;
static uint32_t     speed   = 5000000;
static char        *ifile   = "./epcs-w.bin";
static char        *ofile   = "./epcs-cp.bin";
static uint32_t     s_addr  = 0;
static uint32_t     len_rw  = -1;

static uint8_t  cmd     = 0;
static uint8_t  channel = 0;
static uint16_t value   = 10;

static void print_usage(const char *prog)
{
    printf("Usage: %s [-Dswrcb]\n", prog);
    puts("  -D <dev>    device to use (default /dev/spidev1.1)\n"
         "  -C <cmd>    \r\n"
         "  -a <DAC>    DAC address 0,1,2,3\r\n"
         "  -v <value>  DAC value\r\n"
         "  -s <speed   max speed (Hz)\n"
         "  -w <file>   write chip from file \n"
         "  -r <file>   read chip to file \n"
         "  -c <byte>   fill all cell with <byte>\n"
         "  -b          check chip is bad\n"
         "example:\n"
         "	ad5686.elf -D /dev/spidev4.0 -C 0 -c 5 -v 60000");
    exit(1);
}

static void parse_opts(int argc, char *argv[])
{
    while (1) {
	static const struct option lopts[] = {
	    { "device", 1, 0, 'D' },
	    { "cmd", 1, 0, 'C' },
	    { "channel", 1, 0, 'c' },
	    { "val", 1, 0, 'v' },
	    { "speed", 1, 0, 's' },
	    { "delay", 1, 0, 'w' },
	    { "bpw", 1, 0, 'r' },
	    { "check", 1, 0, 'c' },
	    { "bad", 0, 0, 'b' },
	    { "addr", 1, 0, 'a' },
	    { "len", 1, 0, 'l' },
	    { NULL, 0, 0, 0 },
	};
	int c;

	c = getopt_long(argc, argv, "D:s:w:r:c:b:a:l:g:p:C:c:v:", lopts, NULL);

	if (c == -1) {
	    break;
	}

	switch (c) {
	case 'D':
	    device = optarg;
	    break;
	case 'C':
	    cmd = atoi(optarg);
	    break;
	case 'c':
	    channel = atoi(optarg);
	    break;
	case 'v':
	    value = atoi(optarg);
	    break;
	case 's':
	    speed = atoi(optarg);
	    break;
	case 'w':
	    ifile = optarg;
	    mode  = 'w';
	    break;
	case 'r':
	    ofile = optarg;
	    mode  = 'r';
	    break;
	// case 'c':
	// 	sscanf(optarg, "%x", &onebyte);
	// 	mode = 'c';
	// 	break;
	case 'b':
	    mode = 'b';
	    break;
	case 'g':
	    mode = 'g';
	    break;
	case 'p':
	    mode  = 'p';
	    ifile = optarg;
	    break;
	case 'a':
	    s_addr = atoi(optarg);
	    break;
	case 'l':
	    len_rw = atoi(optarg);
	    break;
	default:
	    print_usage(argv[0]);
	    break;
	}
    }
}
static void print_configure()
{
    printf("Device:\t\t%s\n", device);
    if (mode == 'w') {
	printf("Input file: \t%s\n", ifile);
    }
    else if (mode == 'r') {
	printf("Output file: \t%s\n", ofile);
    }
    printf("Speed:\t\t%d\n\n", speed);
}
static void dev_info(struct ad5686_dev *dev)
{
    printf("device information\n");
    printf(
	    "\tDevice ID       %x %s\n"
	    "\tnum sector      %d \n"
	    "\tpage per sector %d \n"
	    "\tbyte per page   %d \n"
	    "\tsector  size    %d \n"
	    "\ttotal byte      %d \n",
	    dev->pid,
	    dev->desc,
	    (int)dev->num_sector,
	    (int)dev->page_per_sector,
	    (int)dev->byte_per_page,
	    (int)dev->sector_sz,
	    (int)epcs_size(dev));
}

int update_file(struct ad5686_dev *dev, char *filename)
{
    printf("\n****** Start update chip from %s ******\n", filename);
    int ret;

    FILE          *fp;
    unsigned int   dev_size = epcs_size(dev);
    unsigned char *file_buf;
    int            file_len;
    int            wr_len, offset;

    fp = fopen(filename, "rb");
    if (fp == NULL) {
	ret = -1;
	goto _fail1;
    }


    fseek(fp, 0, SEEK_END);
    file_len = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    if (dev_size < file_len) {
	printf("Input file too large,device not enough space!\n");
	ret = -1;
	goto _fail1;
    }
    file_buf = (unsigned char *)malloc(dev_size);
    if (file_buf == NULL) {
	ret = -1;
	goto _fail1;
    }

    offset = 0;
    while (file_len) {
	wr_len = fread(file_buf + offset, 1, file_len, fp);
	file_len -= wr_len;
	offset += file_len;
    }

    fseek(fp, 0, SEEK_END);
    file_len = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    format_chip(dev);
    write_chip(dev, 0, file_buf, dev_size);
    printf("check sum %x\n", ap_hash(file_buf, dev_size));
    read_chip(dev, 0, file_buf, dev_size);
    printf("check sum %x\n", ap_hash(file_buf, dev_size));

    ret = 0;
    free(file_buf);

    fclose(fp);
_fail1:
    if (ret == 0) {
	printf("\n****** Update success ******\n");
    }
    else {
	printf("\n****** Update fail ******\n");
    }
    return ret;
}

int save_file(struct ad5686_dev *dev, char *filename)
{
    printf("\n****** Read chip save as %s ******\n", filename);
    int ret;

    FILE          *fp;
    unsigned long  dev_size = epcs_size(dev);
    unsigned char *file_buf;
    int            file_len;
    int            wr_len, offset;

    fp = fopen(filename, "wb");
    if (fp == NULL) {
	ret = -1;
	goto _fail1;
    }
    file_len = dev_size;
    file_buf = (unsigned char *)malloc(dev_size);
    if (file_buf == NULL) {
	ret = -1;
	goto _fail1;
    }

    read_chip(dev, 0, file_buf, dev_size);
    printf("check sum %x\n", ap_hash(file_buf, dev_size));


    offset = 0;
    while (file_len) {
	wr_len = fwrite(file_buf + offset, 1, file_len, fp);
	printf("wr_len %d\n", wr_len);
	file_len -= wr_len;
	offset += file_len;
    }

    ret = 0;
    free(file_buf);

    fclose(fp);
_fail1:

    if (ret == 0) {
	printf("\n****** Save success ******\n");
    }
    else {
	printf("\n****** Save fail ******\n");
    }
    return ret;
}


int format_sector(struct ad5686_dev *dev, uint32_t addr, uint32_t num)
{
    printf("\nErase sector\n");

    int start = addr / dev->sector_sz;
    while (num--) {
	if (epcs_erase_sector(dev, addr) == -1) {
	    printf("Erase sector fail %d(0x%x)\n", start, start * dev->sector_sz);
	    return -1;
	}
	else {
	    printf("\rErase sector success %d(0x%x)", start, start * dev->sector_sz);
	}
	start++;
	addr += dev->sector_sz;
    }
    return 0;
}


int write_some(struct ad5686_dev *dev, char *filename, uint32_t addr, uint32_t len)
{
    int            ret;
    int            flen;
    FILE          *fp;
    unsigned int   dev_size = epcs_size(dev);
    unsigned char *file_buf;
    int            file_len;
    fp = fopen(filename, "rb");
    if (fp == NULL) {
	ret = -1;
	goto _fail1;
    }


    fseek(fp, 0, SEEK_END);
    file_len = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    if (dev_size < file_len) {
	printf("Input file too large,device not enough space!\n");
	ret = -1;
	goto _fail1;
    }
    if (addr & (dev->sector_sz - 1)) {
	printf("address must align sector %d(0x%x)\r\n", dev->sector_sz, dev->sector_sz);
	goto _fail1;
    }
    printf("flen %d %d %x\r\n", file_len, dev->sector_sz, dev->sector_sz);

    uint32_t need_sector = file_len / dev->sector_sz +
                           (file_len & (dev->sector_sz - 1) ? 1 : 0);
    printf("need sector %d\r\n", need_sector);
    format_sector(dev, addr, need_sector);


    file_buf = (unsigned char *)malloc(dev_size);
    if (file_buf == NULL) {
	ret = -1;
	goto _fail1;
    }

    int wr_len, offset, tlen;

    tlen   = file_len;
    offset = 0;
    while (file_len) {
	wr_len = fread(file_buf + offset, 1, file_len, fp);
	file_len -= wr_len;
	offset += file_len;
    }
    // memset(file_buf, 'b', file_len);

    file_len = tlen;
    printf("write\r\n");
    write_chip(dev, addr, file_buf, file_len);
    printf("end write\r\n");


_fail1:
    if (ret == 0) {
	printf("\n****** Update success ******\n");
    }
    else {
	printf("\n****** Update fail ******\n");
    }
    return 0;
}

int read_some(struct ad5686_dev *dev, char *filename, uint32_t addr, uint32_t len)
{
    printf("get some addr %d len %d\r\n", addr, len);
    printf("byte %d\r\n", (int)dev->byte_per_page);
    printf("page %d\r\n", (int)dev->page_per_sector);


    uint32_t page_total;
    uint32_t page_mask = dev->byte_per_page - 1;
    uint32_t page_size = dev->byte_per_page;
    uint32_t page_addr;
    uint32_t off_data, size = 1024;

    char *pdata = malloc(1024 * 1024);
    page_addr   = addr;
    uint32_t wr_len;

    if (-1 == epcs_read_page(dev, page_addr, pdata, size)) {
	printf("fail epcs_read_page\r\n");
    }

    FILE *fp;
    fp = fopen(filename, "wb");
    if (fp == NULL) {
	return 0;
    }
    wr_len = fwrite(pdata, 1, 100, fp);
    fclose(fp);


    return 0;
}


int fill(struct ad5686_dev *dev, char date)
{
    unsigned long  dev_size;
    unsigned char *ifile;

    printf("\n****** Start fill byte 0x%.2x ******\n", date);
    dev_size = epcs_size(dev);
    ifile    = (unsigned char *)malloc(dev_size);
    if (ifile == NULL) {
	perror("malloc error");
	return -1;
    }

    memset(ifile, date, dev_size);
    format_chip(dev);
    write_chip(dev, 0, ifile, dev_size);
    free(ifile);
    printf("\n****** Fill byte over ******\n");
    return 0;
}

int isbad(struct ad5686_dev *dev)
{
    printf("\n****** Check bad cell ******\n");
    unsigned long  dev_size;
    unsigned char *ofile;
    dev_size = epcs_size(dev);
    ofile    = (unsigned char *)malloc(dev_size);
    if (ifile == NULL) {
	perror("malloc error");
	return -1;
    }

    // 检查全0xff
    printf("\nStep 1: format device\n");
    format_chip(dev);
    read_chip(dev, 0, ofile, dev_size);
    for (int i = 0; i < dev_size; i++) {
	if (ofile[i] != 0xff) {
	    goto _fail;
	}
    }
    printf("\nStep 2: write 0x00 to all cell\n");
    // 检查全0x00
    bzero(ofile, dev_size);
    write_chip(dev, 0, ofile, dev_size);
    memset(ofile, 1, dev_size);
    read_chip(dev, 0, ofile, dev_size);
    for (int i = 0; i < dev_size; i++) {
	if (ofile[i] != 0x00) {
	    printf("i %d", i);
	    goto _fail;
	}
    }
    printf("\n****** Chip is good ******\n");


    free(ofile);
    return 0;
_fail:
    printf("\n****** Chip is bad ******\n");
    free(ofile);
    return -1;
}

int main(int argc, char *argv[])
{
    // epcs_close(NULL);
    int ret = 0;
    // int fd;
    parse_opts(argc, argv);
    print_configure();
    struct ad5686_dev *dev;
    ret = ad5686_open(device, &dev, speed);
    switch (ret) {
    case 0:
	printf("open device success!\n");
	break;
    case -1:
	printf("bus error\n");
	break;
    case -2:
	printf("unknow device ID\n");
	return -1;
    }
    if (dev == 0) {
	return 0;
    }

    printf("Cmd %x ch %x val %x\r\n", cmd, channel, value);
    // ad5686_write(1, value);
    // ad5686_write(2, value);
    // ad5686_write(4, value);
    // ad5686_write(8, value);
    //  exit(1);
    // ad5686_power_mode(dev, 0,AD5686_PWRM_100K);
    // ad5686_power_mode(dev, 1,AD5686_PWRM_100K);
    // ad5686_power_mode(dev, 2,AD5686_PWRM_100K);
    // ad5686_power_mode(dev, 3,AD5686_PWRM_100K);
    ad5686_write_update_register(dev, channel, value);
    // ad5686_update_register(dev, 0);
    // ad5686_write_update_register(dev, 1, value);
    // ad5686_update_register(dev, 1);
    // ad5686_write_update_register(dev, 2, value);
    // ad5686_update_register(dev, 2);
    // ad5686_write_update_register(dev, 3, value);
    // ad5686_update_register(dev, 3);


    // ad5686_write_update_register(dev, 0, value);
    // ad5686_update_register(dev, 0);

    // ad5686_write_update_register(dev, 1, value/2);
    // ad5686_update_register(dev, 1);

    // ad5686_write_update_register(dev, 2, value/3);
    // ad5686_update_register(dev, 2);

    // ad5686_write_update_register(dev, 3, value/4);
    // ad5686_update_register(dev, 3);
    ad5686_close(dev);


    return 0;
}


int format_chip(struct ad5686_dev *dev)
{
    printf("\nErase Chip\n");
    if (epcs_erase_bulk(dev) == -1) {
	printf("Erase timeout\n");
	return -1;
    }
    else {
	printf("\nErase Finish\n");
	return 0;
    }
}

unsigned int write_chip(struct ad5686_dev *dev, uint32_t addr, unsigned char *pdata, unsigned int len)
{
    unsigned long page_total;
    unsigned long page_mask = dev->byte_per_page - 1;
    unsigned long page_size = dev->byte_per_page;
    unsigned long page_addr;
    unsigned long off_data, size;
    // epcs_size(dev);

    //计算文件大小，确定需写入多少Page总数
    page_total = ((len + page_mask) & (~page_mask)) / page_size;


    // Step 2: 写入文件
    page_addr = 0;
    page_addr = addr;
    off_data  = 0;
    while (off_data < len) {
	//当写入的内容大于1页容量，则写入1页大小，否则写入剩余大小
	if (len - off_data > page_size) {
	    size = page_size;
	}
	else {
	    size = len - off_data;
	}


	printf("\rwrite page %8.8x", (unsigned int)page_addr);
	if (-1 == epcs_write_page(dev, page_addr, pdata + off_data, size)) {
	    printf("fail epcs_write_page\r\n");
	    break;
	}
	// while(epcs_read_status(dev) != 0);


	//跟新数据偏偏移量和页面地址
	off_data += page_size;
	page_addr += page_size;
    }
    printf("\n");
    return 0;
}
unsigned int read_chip(struct ad5686_dev *dev, uint32_t addr, unsigned char *pdata, unsigned int len)
{
    unsigned long page_total;
    unsigned long page_mask = dev->byte_per_page - 1;
    unsigned long page_size = dev->byte_per_page;
    unsigned long page_addr;
    unsigned long off_data, size;


    //计算文件大小，确定需读出多少Page总数
    page_total = ((len + page_mask) & (~page_mask)) / page_size;

    printf("Total %d page be read (0x%8.8x ~ 0x%8.8x)\n",
           (int)page_total,
           0,
           (int)(0 + page_total * page_size - 1));


    //等待空闲
    //避免在epcs4_read_file调用前有擦除、写入等操作造成第一次读出数据有错

    // Step 1:读出数据
    page_addr = 0;
    page_addr = addr;
    off_data  = 0;
    while (off_data < len) {
	if (len - off_data > page_size) {
	    size = page_size;
	}
	else {
	    size = len - off_data;
	}

	//写入前等待空闲状态
	// while(epcs_read_status(dev) != 0);

	printf("\rread page %8.8x", (unsigned int)page_addr);

	if (-1 == epcs_read_page(dev, page_addr, pdata + off_data, size)) {
	    printf("fail epcs_read_page\r\n");
	    break;
	}


	//跟新数据偏偏移量和页面地址
	off_data += page_size;
	page_addr += page_size;
    }
    printf("\n");
    return 0;
}


unsigned int ap_hash(unsigned char *data, unsigned int len)
{
    long hash = 0xaaaaaaaa;
    for (int i = 0; i < len; i++) {
	if ((i & 1) == 0) {
	    hash ^= ((hash << 7) ^ data[i] ^ (hash >> 3));
	}
	else {
	    hash ^= (~((hash << 11) ^ data[i] ^ (hash >> 5)));
	}
    }
    return hash;
}


#ifdef PRJ_NAME
PROG_INFO(project, PRJ_NAME);
#endif

#if defined(PRJ_VERSION) && defined(PRJ_PATCHLEVEL) && defined(PRJ_SUBLEVEL)
PROG_INFO(version, PRJ_VERSION "." PRJ_PATCHLEVEL "." PRJ_SUBLEVEL);
#endif

#ifdef BUILD_DATE
PROG_INFO(build, BUILD_DATE);
#endif

PROG_INFO(Author, "MenglongWu");
PROG_INFO(depend, "libepcsx.so");
PROG_INFO(description, "EPCSx download/read tool");
